﻿using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
using Sustainalytics.Entities;
using Sustainalytics.Utils;

namespace Sustainalytics.Blob
{
    public class BlobManager : IBlobManager
    {
        private CloudBlobContainer cloudBlobContainer = null;

        public BlobManager(string connectionString, string rootFolderName, DeploymentLocation deploymentLocation)
        {
            if (string.IsNullOrWhiteSpace(rootFolderName))
            {
                throw new ArgumentException("No root folder specified.");
            }

            switch (deploymentLocation)
            {
                case DeploymentLocation.Development:
                    rootFolderName = string.Format("dev-{0}", rootFolderName.Replace('_', '-')).ToLowerInvariant();
                    break;
                case DeploymentLocation.Test:
                    rootFolderName = string.Format("test-{0}", rootFolderName.Replace('_', '-')).ToLowerInvariant();
                    break;
                case DeploymentLocation.Live:
                    rootFolderName = string.Format("live-{0}", rootFolderName.Replace('_', '-')).ToLowerInvariant();
                    break;
            }

            var storageAccount = CloudStorageAccount.Parse(connectionString);

            var cloudBlobClient = storageAccount.CreateCloudBlobClient();

            cloudBlobClient.RetryPolicy = new Microsoft.WindowsAzure.Storage.RetryPolicies.NoRetry();// turn off the default retry policy as we'll use the custom one defined in Sustainalytics.Utils

            cloudBlobContainer = cloudBlobClient.GetContainerReference(rootFolderName);

            cloudBlobContainer.CreateIfNotExists();
        }

        public static IBlobManager Initialize(string connectionString, string rootFolderName, DeploymentLocation deploymentLocation)
        {
            return new BlobManager(connectionString, rootFolderName, deploymentLocation);
        }

        public Task<bool> DeleteBlob(string blobName)
        {
            return new RetryPolicy().ExecuteAsync(() =>
            {
                var cloudBlockBlob = cloudBlobContainer.GetBlockBlobReference(blobName);

                return new TaskFactory<bool>().FromAsync(cloudBlockBlob.BeginDeleteIfExists((ar) => { }, null), cloudBlockBlob.EndDeleteIfExists);
            });
        }

        public Task<bool> DeleteRootFolder(string rootFolderName)
        {
            ValidateStoragePath(rootFolderName);

            return new RetryPolicy().ExecuteAsync(() =>
            {
                return new TaskFactory<bool>().FromAsync(cloudBlobContainer.BeginDeleteIfExists((ar) => { }, null), cloudBlobContainer.EndDeleteIfExists);
            });
        }

        private void ValidateStoragePath(string relativePath)
        {
            if (!Uri.IsWellFormedUriString(HttpUtility.UrlPathEncode(relativePath), UriKind.Relative) || relativePath == string.Empty)
            {
                throw new ArgumentException(string.Format("The path '{0}' does not comply with the relative URI format.", relativePath ?? "null"));
            }
        }

        private Task UploadBytes(string blobName, byte[] content, string contentType)
        {
            ValidateStoragePath(blobName);

            return new RetryPolicy().ExecuteAsync(() =>
            {
                return Task.Run(() => 
                                {
                                    var blob = cloudBlobContainer.GetBlockBlobReference(blobName);

                                    blob.Properties.ContentType = contentType;

                                    using (var ms = new MemoryStream(content))
                                    {
                                        var uploadFromStream = new TaskFactory().FromAsync(blob.BeginUploadFromStream(ms, (ar) => { }, null), blob.EndUploadFromStream);

                                        uploadFromStream.Wait();
                                    }
                                });
            });
        }

        public Task UploadText(string relativePath, string content)
        {
            return UploadBytes(relativePath, Encoding.UTF8.GetBytes(content), "text/plain");
        }

        public Task UploadBytes(string relativePath, byte[] content)
        {
            return UploadBytes(relativePath, content, "application/octet-stream");
        }

        private Task<byte[]> DownloadBlobBytes(string blobRelativePath)
        {
            return new RetryPolicy().ExecuteAsync(() =>
            {
                var delimiter = cloudBlobContainer.ServiceClient.DefaultDelimiter;                
                var blobAbsolutePath = new Uri(cloudBlobContainer.Uri + (blobRelativePath.StartsWith(delimiter) ? string.Empty : delimiter) + blobRelativePath);
                var taskFactory = new TaskFactory();

                return taskFactory.FromAsync<ICloudBlob>(cloudBlobContainer.ServiceClient.BeginGetBlobReferenceFromServer(blobAbsolutePath, (ar) => { }, null),
                                                            cloudBlobContainer.ServiceClient.EndGetBlobReferenceFromServer)
                                    .ContinueWith(brt =>
                                    {
                                        using (var ms = new MemoryStream())
                                        {
                                            var downloadToStream = taskFactory.FromAsync(brt.Result.BeginDownloadToStream(ms, (ar) => { }, null), brt.Result.EndDownloadToStream);

                                            downloadToStream.Wait();

                                            return ms.ToArray();
                                        }
                                    });
            });
        }

        public Task<string> DownloadText(string blobRelativePath)
        {
            return DownloadBlobBytes(blobRelativePath).ContinueWith(bbt => { return Encoding.UTF8.GetString(bbt.Result); });
        }

        public Task<byte[]> DownloadBytes(string blobRelativePath)
        {
            return DownloadBlobBytes(blobRelativePath);
        }

        public Task<bool> BlobExists(string blobName)
        {
            return new RetryPolicy().ExecuteAsync(() =>
            {
                var cloudBlockBlob = cloudBlobContainer.GetBlockBlobReference(blobName);

                return new TaskFactory<bool>().FromAsync(cloudBlockBlob.BeginExists((ar) => { }, null), cloudBlockBlob.EndExists);
            });
        }
    }
}
